import decimal
from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import render
from rest_framework.decorators import action
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
from user.models import Address
from .models import Classification, Commodity, CommodityImg, ShoppingCart, Order, OrderGoods, Payment
from .serializers import ClassificationSerializer, ClassificationSerializerSimple, CommoditySerializer, \
    ShoppingCartSerializer, OrderSerializer, PaymentSerializer
from rest_framework.permissions import IsAuthenticated
from fdfs_client.client import Fdfs_client, get_tracker_conf
from rest_framework.response import Response
from rest_framework import status, serializers
from datetime import datetime
from celery_tasks.email import tasks
from jt32.settings import alipay
import random
import logging


# Create your views here.
# 商品分类视图集
class ClassificationViewSet(ReadOnlyModelViewSet):
    # 指定模型和序列化器
    queryset = Classification.objects.filter(is_delete=False)
    serializer_class = ClassificationSerializer
    # 必须有登录权限
    permission_classes = [IsAuthenticated]

    # 重写序列化器方法
    def get_serializer_class(self):
        # 如果使用的为list方法
        if self.action == 'list':
            return ClassificationSerializerSimple
        return ClassificationSerializer

    # 改变list方法,第一次只进行展示一级分类的数据,重写模型方法即可
    def get_queryset(self):
        if self.action == 'list':
            return self.queryset.filter(parent__isnull=True)
        return self.queryset

    # 增加方法commodity,获取详情的商品数据(法一,使用反向查询)
    # @action(methods=['get'], detail=True)
    # def commodity(self, request, pk=None):
    #     try:
    #         classification = self.get_queryset().get(id=pk)
    #     except Classification.DoesNotExist:
    #         return Response(status=status.HTTP_404_NOT_FOUND)
    #     # 先进行假设为一级分类
    #     classification_type = 'classification1_set'
    #     classification_name = classification.name
    #     # 判断上面有没有,有的就是两级分类了
    #     if classification.parent:
    #         classification_type = 'classification2_set'
    #         classification_name = f'{classification.parent.name}:{classification.name}'
    #     # 去获取返回数据,用反向查询去得到商品详情
    #     commodity_res = getattr(classification, classification_type)
    #     # 进行序列化并返回
    #     serializer = CommoditySerializer(commodity_res, many=True)
    #     return Response({'commodity': serializer.data, 'classification': classification_name})

    # 增加方法commodity,获取详情的商品数据(法二,使用正向查询)
    @action(methods=['get'], detail=True)
    def commodity(self, request, pk=None):
        try:
            classification = self.get_queryset().get(id=pk)
        except Classification.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        # 先进行假设为一级分类
        classification_type = 'classification1'
        classification_name = classification.name
        # 判断上面有没有,有的就是两级分类了
        if classification.parent:
            classification_type = 'classification2'
            classification_name = f'{classification.parent.name}:{classification.name}'
        # 分装一个字典查询条件
        classification_dict = {
            classification_type: classification,
            'is_delete': False,
            'status': 1
        }
        # 进行正向查询即可
        classification_res = Commodity.objects.filter(**classification_dict)
        # 序列化并进行返回
        serializer = CommoditySerializer(classification_res, many=True)
        return Response({'commodity': serializer.data, 'classification': classification_name})


# 商品分类视图
class CommodityViewSet(ModelViewSet):
    # 指定序列化器和模型
    queryset = Commodity.objects.filter(is_delete=False)
    serializer_class = CommoditySerializer
    # 指定登录权限
    permission_classes = [IsAuthenticated]

    # 重写create方法,给商品图片表也去添加数据
    def create(self, request, *args, **kwargs):
        # 获取前端的数据
        data = request.data
        # 进行反序列化并进行校验
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        # 校验成功后先进行商品详情保存
        serializer.save()
        # 这个时候进行图片的存储,使用fastdfs进行操作,先拿到图片的数据
        files = request.FILES.getlist('file')
        client_address = get_tracker_conf('fastdfs/client.conf')
        client = Fdfs_client(client_address)
        logger = logging.getLogger(__name__)
        # 可能是多张图片,进行循环操作
        for file in files:
            # 先进行判断图片格式是否为png或者jpg格式
            if file.content_type not in ['image/png', 'image/jpeg']:
                return Response({'msg': '图片上传失败,格式不正确'}, status=status.HTTP_400_BAD_REQUEST)
            # 尝试去获取图片的扩展名
            try:
                ext_name = file.name.split('.')[-1]
            except Exception as e:
                logger.info('图片扩展名异常', e)
                ext_name = 'png'
            # 进行fastdfs上传
            try:
                upload_result = client.upload_by_buffer(file.read(), file_ext_name=ext_name)
            except Exception as e:
                logger.error('图片上传错误', e)
                return Response({'msg': '图片上传错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            # 判断图片是否上传呈成功
            if upload_result.get('Status') != 'Upload successed.':
                logger.error('图片上传失败', upload_result)
                return Response({'msg': '图片上传失败'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            # 得到图片地址并存入数据库之中
            image_url = upload_result.get('Remote file_id').decode()
            CommodityImg.objects.create(src=image_url, commodity_id=serializer.data.get('id'))
        # 返回数据
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    # 增加optimization方法,进行展示优选数据
    @action(methods=['get'], detail=False)
    def optimization(self, request):
        # 按照排序规则进行数据展示
        priority_list = self.get_queryset().order_by('-sales', '-comments')
        # 这里就进行展示五条数据
        if len(priority_list) >= 5:
            priority_list = priority_list[:5]
        # 进行序列化返回
        serializer = self.get_serializer(priority_list, many=True)
        return Response(serializer.data)


# 购物车视图类集
class ShoppingCartViewSet(ModelViewSet):
    # 指定模型和序列化器
    queryset = ShoppingCart.objects.all()
    serializer_class = ShoppingCartSerializer
    # 添加权限
    permission_classes = [IsAuthenticated]

    # 更改模型,只可以看到自己的购物车
    def get_queryset(self):
        return self.request.user.shoppingcart_set.all()

    # 重写create方法
    def create(self, request, *args, **kwargs):
        # 允许对POST的数据进行修改,防止用户互相修改和增加数据
        request.POST._mutable = True
        request.data['user'] = request.user.id
        # 获取前端传过来的数据
        commodity_id = request.data['commodity']
        number = request.data['number']
        # 去数据库寻找是否存在了这个数据
        exist_item = self.get_queryset().filter(user=request.user, commodity_id=commodity_id).first()
        # 如果存在,调用update方法
        if exist_item:
            # 对数据进行序列化并进行校验
            serializer = self.get_serializer(instance=exist_item, data={
                'number': exist_item.number + number,
                'user': request.user.id,
                'commodity': commodity_id
            })
            serializer.is_valid(raise_exception=True)
            serializer.save()
            return Response(serializer.data)
        # 若不存在直接调用父类ModelViewSet的create方法
        else:
            return ModelViewSet.create(self, request, *args, **kwargs)


# 订单视图类集
class OrderViewSet(ModelViewSet):
    # 指定模型和序列化器
    queryset = Order.objects.all()
    serializer_class = OrderSerializer
    # 添加权限控制
    permission_classes = [IsAuthenticated]

    # 重写模型,用户只可以看到自己的订单,不可以看见别人的
    def get_queryset(self):
        return self.request.user.order_set.all()

    # 重写create方法,对数据进行完整添加
    def create(self, request, *args, **kwargs):
        # {address: 5, pay_method: "1", cart: ["2", "1"]}
        # 获取从前端传过来的数据
        data = request.data
        user = request.user
        pay_method_id = data['pay_method']
        address_id = data['address']
        carts = data['cart']
        # 对购物车进行校验
        if not carts:
            return Response({'message': '购物车不能为空'}, status=status.HTTP_400_BAD_REQUEST)
        # 对地址和进行校验
        try:
            address = Address.objects.get(id=address_id)
        except Address.DoesNotExist:
            return Response({'message': '地址不存在'}, status=status.HTTP_400_BAD_REQUEST)
        # 地址实际存在,去获取详细的信息,包括省市区等
        province = address.province
        city = address.city
        district = address.district
        receiver = address.receiver
        mobile = address.mobile
        address_res = f'''{province.name}
        {city.name}
        {district.name}
        【{receiver} 收 】
        {mobile}'''
        # 创建好订单号
        order_id = datetime.now().strftime('%Y%m%d%H%M%S%f') + '%6d%2d' % (request.user.id, random.randint(0, 99))
        # 提前创建好需要的Order模型并且下面涉及很多的数据库操作,需要开启事务
        with transaction.atomic():
            # 设置回滚起点
            start_point = transaction.savepoint()
            try:
                order = Order.objects.create(
                    order_id=order_id,
                    pay_method=1 if pay_method_id == '1' else 2,
                    address=address_res,
                    status=1,
                    total_count=0,
                    total_amount=decimal.Decimal('0'),
                    user=user
                )
                # 对购物车进行循环(在里面进行增加乐观锁,防止多人高并发数据错误)
                for cart_id in carts:
                    while True:
                        # 先得到购物车的模型对象
                        try:
                            cart = ShoppingCart.objects.get(id=cart_id)
                        except ShoppingCart.DoesNotExist:
                            raise ValueError('购物车数据不存在')
                        # 先去获取该商品的信息
                        commodity = cart.commodity
                        # 获取原有的库存和销量
                        old_stock = commodity.stock
                        old_sales = commodity.sales
                        # 先进行判断现在要购买的数据是否充足
                        if commodity.stock < cart.number:
                            raise ValueError('库存不足')
                        # 更新库存和销量
                        new_stock = old_stock - cart.number
                        new_sales = old_sales + cart.number
                        # commodity.stock = new_stock
                        # commodity.sales = new_sales
                        # commodity.save()
                        # 以上三句改为使用update方法进行修改,若修改成功则返回1,失败则返回0
                        res = Commodity.objects.filter(id=commodity.id, stock=old_stock).update(
                            stock=new_stock, sales=new_sales
                        )
                        # 判断这个res是否存在
                        if not res:
                            # 回滚
                            continue
                        # 进行更新每次的数量和总价
                        order.total_amount = order.total_amount + (commodity.price * cart.number)
                        order.total_count = order.total_count + cart.number
                        # 这里需要对订单详情表进行数据增加
                        OrderGoods.objects.create(
                            number=cart.number,
                            price=commodity.price,
                            order=order,
                            commodity=commodity
                        )
                        break
                # 循环结束保存最终的
                order.save()
            # 捕捉到异常进行回滚
            except Exception as e:
                transaction.savepoint_rollback(start_point)
                return Response({'msg': str(e)}, status=status.HTTP_400_BAD_REQUEST)
            # 成功并无异常
            else:
                transaction.savepoint_commit(start_point)
        # 删除购物车模型数据
        ShoppingCart.objects.filter(id__in=carts).delete()
        # 序列化order数据并进行返回
        serializer = self.get_serializer(order)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    # 增加status自定义方法
    @action(methods=['PUT'], detail=False)
    def status(self, request):
        # 获取网页中的提交参数
        data = request.query_params.dict()
        # 获取订单号
        out_trade_no = data.get('out_trade_no')
        # 获取交易号
        trade_no = data.get('trade_no')
        # 开启事务并创建初始点进行数据库操作
        with transaction.atomic():
            start_point = transaction.savepoint()
            try:
                Payment.objects.create(trade_id=trade_no, order_id=out_trade_no)
                self.queryset.filter(order_id=out_trade_no, status=1).update(status=2)
            except Exception as e:
                transaction.savepoint_rollback(start_point)
                raise serializers.ValidationError(str(e))
            else:
                transaction.savepoint_commit(start_point)
                # 订单字符成功了,此时使用celery进行邮件的发送
                # 获取商品小票数据
                ordergoods_res = self.get_queryset().get(order_id=out_trade_no).ordergoods_set.all()
                ordergood_li = []
                for ordergood in ordergoods_res:
                    ordergood_li.append(f'<h3>您购买了{ordergood.commodity.name}, 数量为{ordergood.number}, 价格是{ordergood.price}</h3>')
                # 获取收件人邮箱
                email = Order.objects.get(order_id=out_trade_no).user.email
                tasks.send_email.delay(email, f'''
                                       <h1>恭喜下单成功</h1>
                                       <h3>f{ordergood_li}</h3>
                                        ''')
        return Response({'order_id': out_trade_no, 'trade_id': trade_no}, status=status.HTTP_201_CREATED)


# 支付订单视图类集
class PaymentViewSet(ReadOnlyModelViewSet):
    # 指定模型和序列化器
    queryset = Payment.objects.all()
    serializer_class = PaymentSerializer
    # 添加权限控制
    permission_classes = [IsAuthenticated]

    # 增加自定义方法pay
    @action(methods=['GET'], detail=True)
    def pay(self, request, pk=None):
        # 获取目标(根据订单号,用户,状态)
        try:
            order = Order.objects.get(order_id=pk, user=request.user, status=1)
        except Order.DoesNotExist:
            return Response({'message': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
        # 成功找到订单,对setting中的类进行配置
        order_res = alipay.api_alipay_trade_page_pay(
            subject='jutong支付订单',
            out_trade_no=order.order_id,
            total_amount=str(order.total_amount),
            return_url='http://127.0.0.1:8080/order/success'
        )
        # 调用支付宝支付接口
        pay_url = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do?' + order_res
        return Response({'pay_url': pay_url})
